# File: Lexer.rb
# Authors: Jenny Valdez, Jesus Martinez
#     In this file it is defined the lexer class.

require "Tokens"

# A lexer's job is to convert a sequence of characters into a sequence of tokens,
# being the tokens defined in another class.
class Lexer

  attr_reader :line, :col

  # Initialize the lexer
  def initialize(input)
    @line = 1
    @col = 1
    if input.kind_of? String
      @input = nil
      @buffer = input
    else
      @input = input
      @buffer = @input.gets
    end
  end

  # Skips a character of n-size
  def skip(n=1)
    @buffer = @buffer[ n .. -1 ]
    @col = @col + n
  end

  # Makes the buffer jump from this line
  def nl()
    if @input
      @buffer = @input.gets
    else
      skip(1)
    end
    @line = @line + 1
    @col = 1
  end

  # Returns true if there is nothing else to do. False otherwise.
  def empty
    if @input
	    return @input.eof?
    else
	    return @buffer.empty?
    end
  end

  # yylex returns a message if a token was found,
  # nil if it reached the end of the file or an exception
  # for an unexpected character.
  def yylex
    cl = @line
    cc = @col
    while true
      case @buffer
        # "White" characters and comments
        when /\A(\n|\r)/
          begin
              nl()
              cl = @line
              cc = @col
		      end
		    when /\A(\#)/
		      begin
		        if @input
		          nl()
                cl = @line
                cc = @col
			      else
			        skip(@buffer.length)
		        end
		      end
        when /\A( |\t)+/
          begin
            skip($&.length)
            cc = @col
          end

        # Strings and numbers
        when /\A(((\d)+(\.(\d)*)?)|(\.(\d)+))/
          begin
            skip($&.length)
            return TkNum.new(cl, cc, $&)
          end
        when /\A((".*[^\/]")|('.*[^\/]'))/
          begin
			      y = $&.length
            skip($&.length)
            return TkString.new(cl, cc, $&[1..y-2])
          end

        # 2-size characters
        when /\A(:=)/
          begin
            skip(2)
            return TkAssign.new(cl, cc)
          end
        when /\A(&&)/
          begin
            skip(2)
            return TkAnd.new(cl, cc)
          end
        when /\A(\|\|)/
          begin
            skip(2)
            return TkOr.new(cl, cc)
          end
        when /\A(!=)/
          begin
            skip(2)
            return TkNotEqual.new(cl, cc)
          end
        when /\A(<=)/
          begin
            skip(2)
            return TkLessThanEqual.new(cl, cc)
          end
        when /\A(>=)/
          begin
            skip(2)
            return TkGreaterThanEqual.new(cl, cc)
          end
        when /\A(\*\*)/
          begin
            skip(2)
            return TkPower.new(cl, cc)
          end

        # 1-size characters
        when /\A\+/
          begin
            skip(1)
            return TkPlus.new(cl, cc)
          end
        when /\A-/
          begin
            skip(1)
            return TkMinus.new(cl, cc)
          end
        when /\A\*/
          begin
            skip(1)
            return TkMinus.new(cl, cc)
          end
        when /\A\//
          begin
            skip(1)
            return TkDiv.new(cl, cc)
          end
        when /\A%/
          begin
            skip(1)
            return TkModulo.new(cl, cc)
          end
        when /\A</
          begin
            skip(1)
            return TkLessThan.new(cl, cc)
          end
        when /\A>/
          begin
            skip(1)
            return TkGreaterThan.new(cl, cc)
          end
        when /\A!/
          begin
            skip(1)
            return TkNot.new(cl, cc)
          end
        when /\A=/
          begin
            skip(1)
            return TkEqual.new(cl, cc)
          end
        when /\A^/
          begin
            skip(1)
            return TkExp.new(cl, cc)
          end
        when /\A@/
          begin
            skip(1)
            return TkAt.new(cl, cc)
          end
        when /\A\$/
          begin
            skip(1)
            return TkDollar.new(cl, cc)
          end
        when /\A\{/
          begin
            skip(1)
            return TkLeftBrace.new(cl, cc)
          end
        when /\A\}/
          begin
            skip(1)
            return TkRightBrace.new(cl, cc)
          end
        when /\A\[/
          begin
            skip(1)
            return TkLeftBracket.new(cl, cc)
          end
        when /\A\]/
          begin
            skip(1)
            return TkRightBracket.new(cl, cc)
          end
        when /\A\(/
          begin
            skip(1)
            return TkLeftParentheses.new(cl, cc)
          end
        when /\A\)/
          begin
            skip(1)
            return TkRightParentheses.new(cl, cc)
          end
        when /\A,/
          begin
            skip(1)
            return TkComma.new(cl, cc)
          end
        when /\A\./
          begin
            skip(1)
            return TkPeriod.new(cl, cc)
          end
        when /\A:/
          begin
            skip(1)
            return TkColon.new(cl, cc)
          end
        when /\A;/
          begin
            skip(1)
            return TkSemiColon.new(cl, cc)
          end

        # Words
        when /\A[a-zA-Z](\w)*/
          begin
              skip($&.length)
              word = $&
            case word.downcase
              when "define"
                return TkDefine.new(cl, cc)
              when "of"
                return TkOf.new(cl, cc)
              when "type"
                return TkType.new(cl, cc)
              when "as"
                return TkAs.new(cl, cc)
              when "begin"
                return TkBegin.new(cl, cc)
              when "end"
                return TkEnd.new(cl, cc)
              when "vars"
                return TkVars.new(cl, cc)
              when "if"
                return TkIf.new(cl, cc)
              when "then"
                return TkThen.new(cl, cc)
              when "else"
                return TkElse.new(cl, cc)
              when "while"
                return TkWhile.new(cl, cc)
              when "foreach"
                return TkForeach.new(cl, cc)
              when "in"
                return TkIn.new(cl, cc)
              when "do"
                return TkDo.new(cl, cc)
              when "num"
                return TkNumID.new(cl, cc)
              when "vec"
                return TkVec.new(cl, cc)
              when "mat"
                return TkMat.new(cl, cc)
              when "read"
                return TkRead.new(cl, cc)
              when "write"
                return TkWrite.new(cl, cc)
              when "return"
                return TkReturn.new(cl, cc)
              when "zeroes"
                return TkZeroes.new(cl, cc)
              when "range"
                return TkRange.new(cl, cc)
              when "eye"
                return TkEye.new(cl, cc)
              when "true", "false"
                return TkBoolean.new(cl, cc, $&)
              else
                return TkId.new(cl, cc, $&)
            end
          end
        else
		      # If there is nothing else to do...
          if empty then
            return nil
		      # Unexpected character
          else
            x = @buffer[0].chr
            skip(1)
            raise "ERROR: Caracter no esperado '#{x}' en linea #{cl}, columna #{cc}."
          end
      end
    end
  end

end